package org.streets.eis.component.foreigncombobox;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.wicket.Response;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.tree.BaseTree;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.string.Strings;
import org.streets.commons.util.JDBCUtils;
import org.streets.database.Limits;
import org.streets.database.SQLConnection;
import org.streets.database.SQLNestedException;

import org.streets.eis.component.WebResources;
import org.streets.eis.component.combobox.ComboBox;
import org.streets.eis.component.combobox.TreeComboBox;
import org.streets.eis.component.model.DataModel;
import org.streets.eis.component.model.ListItem;

@SuppressWarnings("unchecked")
public class ForeignComboBox extends ComboBox {

	private static final long serialVersionUID = 1L;

	private ForeignDefinition definition; 

	private boolean onlySelectLeaf;
	
	private boolean isNested;
	
	protected Map<String,DataModel<ListItem>> idModels = new HashMap<String,DataModel<ListItem>>();	

	@Inject
	private SQLConnection conn;
	
	public ForeignComboBox(String id, ForeignDefinition definition){
		this(id,definition,false);
	}
	
	public ForeignComboBox(String id, ForeignDefinition definition,boolean canInput){
		this(id,definition,canInput,false);
	}
	
	public ForeignComboBox(String id, ForeignDefinition definition,boolean canInput,boolean onlySelectLeaf){
		super(id,null,canInput);
		this.onlySelectLeaf = onlySelectLeaf;
		setDefinition(definition);		
	}

	public ForeignComboBox(String id, IModel<ListItem> model,ForeignDefinition definition){
		this(id,model,definition,false);
	}
	
	public ForeignComboBox(String id, IModel<ListItem> model,ForeignDefinition definition,boolean canInput){
		this(id,model,definition,canInput,false);
	}
	
	
	public ForeignComboBox(String id, IModel<ListItem> model,ForeignDefinition definition,boolean canInput,boolean onlySelectLeaf){
		super(id,model,null,canInput);
		this.onlySelectLeaf = onlySelectLeaf;
		setDefinition(definition);		
	}
	
	public boolean getOnlySelectLeaf(){
		return onlySelectLeaf;
	}
	
	public ForeignComboBox setOnlySelectLeaf(boolean onlySelectLeaf){
		this.onlySelectLeaf = onlySelectLeaf;
		return this;
	}
	
	public ForeignDefinition getDefinition() {
		return definition;
	}

	public void setDefinition(ForeignDefinition definition) {
		this.definition = definition;
		isNested = definition!=null && !Strings.isEmpty(definition.getParentField());
		setDataModel(generateCodeGroupDataModel());
	}
	
	protected String generateSql4DataModel(){
		String cond = definition.getConditionSql();
		cond = Strings.isEmpty(cond) ? "" : " where " + cond;
		if (!isNested){
			return String.format("select %s,%s from %s  %s order by %s",
					definition.getIdField(),definition.getTextField(),definition.getTableName(),cond,definition.getOrderSql());		
		} else {
			return String.format("select %s,%s,(case when exists(select %s from %s a where a.%s=%s.%s) then 1 else 0 end) from %s %s order by %s",
					definition.getIdField(),definition.getTextField(),
					definition.getIdField(),definition.getTableName(),definition.getParentField(),definition.getTableName(),definition.getIdField(),
					definition.getTableName(),cond,definition.getOrderSql());
		}
	}
	
	public Object generateCodeGroupDataModel(){
		if (definition == null){
			return new ArrayList<ListItem>(); 
		} 
		
		String sql = generateSql4DataModel();
		ResultSet rs = conn.query(sql, Limits.none());
		try {
			if (!isNested){
				List<ListItem> model = new ArrayList<ListItem>();
				while (rs.next()){
					model.add(new ListItem(rs.getString(1),rs.getString(2)));
				}
				return model;
			} else {
				DataModel<ListItem> model = new DataModel<ListItem>();
				while (rs.next()){
					DataModel<ListItem> tmp = new DataModel<ListItem>(new ListItem(rs.getString(1),rs.getString(2)));
					if (rs.getInt(3)==1) tmp.addChild(TreeComboBox.DUMMY);
					model.addChild(tmp);
				}
				return model;
			}
		} catch (Exception ex) {
			throw new SQLNestedException(sql, ex);
		} finally {
			JDBCUtils.close(rs);
		}
	}
	
	protected String generateSql4MatchedItems(String key){
		String cond = definition.getConditionSql();
		cond = Strings.isEmpty(cond) ? "" : " where " + cond;
		if (!isNested){
			return String.format("select %s,%s from %s  %s order by %s",
					definition.getIdField(),definition.getTextField(),definition.getTableName(),cond,definition.getOrderSql());		
		} else {
			String filterSql = "";
			if (getOnlySelectLeaf()){
				filterSql = String.format("and not exists(select %s from %s b where %s=%s.%s)",
						definition.getIdField(),definition.getTableName(),definition.getParentField(),definition.getTableName(),definition.getIdField());
			}

			if (Strings.isEmpty(definition.getConditionSql())){
				return String.format("select %s,%s from %s where %s %s order by %s",
						definition.getIdField(),definition.getTextField(),definition.getTableName(),generateLikeSql(definition.getTextField(),key),filterSql,definition.getOrderSql());	
			} else {
				return String.format("select %s,%s from %s where %s  %s start with %s connect by %s=prior %s order by %s",
						definition.getIdField(),definition.getTextField(),definition.getTableName(),generateLikeSql(definition.getTextField(),key),filterSql,
						definition.getConditionSql(),definition.getParentField(),definition.getIdField(),definition.getOrderSql());		
			}
		}
	}
	
	@Override
	protected Iterator<ListItem> getMatchedItems(String key) {
		if (definition == null){
			return new ArrayList<ListItem>().iterator();
		} 
		
		String sql = generateSql4MatchedItems(key) ;
		ResultSet rs = conn.query(sql, Limits.of(0, 10));
		List<ListItem> matcheds = new ArrayList<ListItem>();
		try{
			while (rs.next()){
				matcheds.add(new ListItem(rs.getString(1),rs.getString(2)));
			}
		} catch (Exception ex) {
			throw new SQLNestedException(sql, ex);
		} finally {
			JDBCUtils.close(rs);
		}
		return matcheds.iterator();	
	}
	
	protected String generateSql4Children(String parentId){
		return String.format("select %s,%s,(case when exists(select %s from %s a where a.%s=%s.%s) then 1 else 0 end) from %s  where %s = '%s' order by %s",				
				definition.getIdField(),definition.getTextField(),
				definition.getIdField(),definition.getTableName(),definition.getParentField(),definition.getTableName(),definition.getIdField(),
				definition.getTableName(),definition.getParentField(),parentId,definition.getOrderSql());
	}
	
	protected List<DataModel<ListItem>> generateChildrenDataModel(String parentId) {
		if (definition==null){
			return new ArrayList<DataModel<ListItem>>();
		} 
		String sql =  generateSql4Children(parentId);

		ResultSet rs = conn.query(sql, Limits.none());
		DataModel<ListItem> model = new DataModel<ListItem>();
		try{
			while (rs.next()){
				DataModel<ListItem> tmp = new DataModel<ListItem>(new ListItem(rs.getString(1),rs.getString(2)));
				if (rs.getInt(3)==1) tmp.addChild(TreeComboBox.DUMMY);
				model.addChild(tmp);
			}
		} catch (Exception ex) {
			throw new SQLNestedException(sql, ex);
		} finally {
			JDBCUtils.close(rs);
		}
		return model.getChildren();
	}
	
	protected DataModel<ListItem> getChildrenDataModel(String parentId){
		DataModel<ListItem> model = idModels.get(parentId);
		if (model==null){
			throw new RuntimeException("Unable to find DataModel<ListItem> of Node : id = " + parentId);
		}
		if (model.getChildrenCount()==0 || model.getChild(0).getObject()==null){
			model.getChildren().clear();
			model.getChildren().addAll(generateChildrenDataModel(parentId));
		}
		return model;
	}
	

	protected void renderDataModel(Response response,DataModel<ListItem> model){
		if (!isNested){
			response.write("<UL>");
			if (!isRequired()){
				renderItem(ListItem.empty(),response);
			}
			if (getDataModel()!=null){
				List<ListItem> dataModel = (List<ListItem>) getDataModel();
				for (Iterator<ListItem> iter = dataModel.iterator(); iter.hasNext();) {
					ListItem item = iter.next();
					if (item!=null){
						renderItem(item,response);
					}
				}
			}
			response.write("</UL>");
		} else {
			response.write("<UL class=\"tree\">");
			if (model==getDataModel() && !isRequired()){
				renderTreeNode(new DataModel<ListItem>(ListItem.empty()),response);
			}
			if (model!=null){
				for (Iterator<DataModel<ListItem>> iter = model.getChildren().iterator(); iter.hasNext();) {
					DataModel<ListItem> m = iter.next();
					if (m!=null){
						renderTreeNode(m,response);
						idModels.put(m.getObject().getId(), m);
					}
				}
			}
			response.write("</UL>");
		}
	}
	
	protected void renderTreeNode(DataModel<ListItem> model,Response response){
		ListItem item = model.getObject();
		String textValue = item.getText();
		if (textValue==null||textValue.trim().length()==0) textValue = "&nbsp;";
		textValue = textValue.trim();
		String id = item.getId();	

        boolean hasChild = model.getChildrenCount()>0;
        if (hasChild&&Strings.isEmpty(getItemIcon(item,false))){
        	item.setIcon(BaseTree.class.getName()+"/res/folder-closed.gif");
        }
		
        response.write("<li><div  i=\""+id+"\" >");

        String className = hasChild ? "closed" : "item";        
        response.write("<a class=\""+className+"\" />");
        
        response.write("<a style=\"width:16px;height:16px;vertical-align:middle;background: url(");
		response.write(getItemIcon(item,true));
		response.write(") left center no-repeat\" />");

		response.write("<a>");
		response.write(textValue);
		response.write("</a></div>");
		
		if (hasChild){
			response.write("<DIV class=\"children\"/>");
		}
		response.write("</li>");
	}
	
	public void renderHead(IHeaderResponse response) {
		if (!isNested){
			response.renderCSSReference(WebResources.Streets.ComboBox.LIST_CSS);
			response.renderJavascriptReference(WebResources.Streets.ComboBox.LIST_JS);
		} else {
			response.renderCSSReference(WebResources.Streets.ComboBox.TREE_CSS);
			response.renderJavascriptReference(WebResources.Streets.ComboBox.TREE_JS);		
		}		
	}	
	
	@Override
	protected String getInitJavascript(CharSequence callbackUrl,String SettingsJS, String indicatorId) {
		final String id = getMarkupId();
		String initJS = "";
		if (!isNested){
			initJS = String.format("var %s=new Streets.ListComboBox('%s','%s',%s,%s);", id,id,
					callbackUrl, SettingsJS, indicatorId);

		} else {
			initJS = String.format("var %s = new Streets.TreeComboBox('%s','%s',%s,%s,%s);", id,id,
					callbackUrl, SettingsJS, indicatorId,onlySelectLeaf);
		}
		return initJS;		
	}

	@Override
	protected void onIconClicked(Response response) {
		if (!isNested){
			renderDataModel(response,null);
		} else {
			String parentId = getRequest().getParameter("p");
			if (parentId!=null&&parentId.length()>0){
				renderDataModel(response,getChildrenDataModel(parentId));
			}else{
				renderDataModel(response,(DataModel<ListItem>)getDataModel());
			}
		}		
	}

	@Override
	protected void onItemChanged(ListItem newValue){
		onItemChanged(this,newValue);
	}

	@Override
	protected void onAjaxItemChanged(ListItem newValue,AjaxRequestTarget target){
		onAjaxItemChanged(this,newValue,target);
	}		
	
	protected void onItemChanged(ForeignComboBox codeGroup,ListItem newValue){		
	}
	
	protected void onAjaxItemChanged(ForeignComboBox codeGroup,ListItem newValue,AjaxRequestTarget target){	
	}
	
}
